// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.

package org.jetbrains.kotlin.nj2k

import org.jetbrains.kotlin.utils.SmartList
import kotlin.random.Random

interface JKElementInfo

interface JKElementInfoLabel {
    fun render(): String
}

@JvmInline
value class JKElementInfoInferenceLabel(private val label: String) : JKElementInfoLabel {
    override fun render(): String = "/*@@$label@@*/"

    companion object {
        val LABEL_REGEX = """/\*@@(\w+)@@\*/""".toRegex()
    }
}

/**
 * Label for optional elements from the original Java code that must be preserved in the resulting Kotlin code.
 *
 * For example, explicit `this` receiver and clarifying parentheses.
 *
 * A particular code style may require to use explicit `this` references in Java even when they are not necessary.
 * We want to preserve such references in the resulting Kotlin code in order to not force the user to manually add them back.
 * Since we run a "Redundant explicit 'this'" inspection in post-processing, we must mark those `this` references
 * that originally came from Java code and were not generated by the J2K itself. Same goes for clarifying parentheses.
 */
@JvmInline
value class JKElementInfoExplicitLabel(private val label: String) : JKElementInfoLabel {
    override fun render(): String = "/*~~$label~~*/"

    companion object {
        val LABEL_REGEX = """/\*~~(\w+)~~\*/""".toRegex()
    }
}

fun String.asInferenceLabel(): JKElementInfoLabel? =
    JKElementInfoInferenceLabel.LABEL_REGEX.matchEntire(this)?.groupValues?.getOrNull(1)?.let { JKElementInfoInferenceLabel(it) }

fun String.asExplicitLabel(): JKElementInfoLabel? =
    JKElementInfoExplicitLabel.LABEL_REGEX.matchEntire(this)?.groupValues?.getOrNull(1)?.let { JKElementInfoExplicitLabel(it) }

class JKElementInfoStorage {
    private val labelToInfo = mutableMapOf<JKElementInfoLabel, MutableList<JKElementInfo>>()
    private val elementToLabel = mutableMapOf<Any, JKElementInfoLabel>()

    fun getOrCreateInferenceLabelForElement(element: Any): JKElementInfoLabel =
        elementToLabel.getOrPut(element) { JKElementInfoInferenceLabel(createRandomString()) }

    fun getOrCreateExplicitLabelForElement(element: Any): JKElementInfoLabel =
        elementToLabel.getOrPut(element) { JKElementInfoExplicitLabel(createRandomString()) }

    fun getInfoForLabel(label: JKElementInfoLabel): List<JKElementInfo>? =
        labelToInfo[label]

    fun addEntry(element: Any, info: JKElementInfo) {
        val label = elementToLabel.getOrPut(element) { JKElementInfoInferenceLabel(createRandomString()) }
        labelToInfo.getOrPut(label) { SmartList() } += info
        elementToLabel[element] = label
    }

    companion object {
        private val charPool: List<Char> = ('a'..'z').toList()
        private const val GENERATED_STRING_LENGTH: Int = 6

        private fun createRandomString(): String = (1..GENERATED_STRING_LENGTH).joinToString("") {
            charPool[Random.nextInt(0, charPool.size)].toString()
        }
    }
}